#include "Pf.h"

#ifdef PF_PLATFORM_PC

#include "PfFileIO.h"
#include <assert.h>

namespace pf
{
	namespace FileIO
	{
		static const size_t cMaxSimultaneousIORequests = 32;

		struct IORequest
		{
			IORequest() :
				hFile(INVALID_HANDLE_VALUE)
			{
			}
			HANDLE hFile;
			OVERLAPPED	overlapped;
			bool busy;
		};

		static IORequest ioRequests[cMaxSimultaneousIORequests];

		FileHandle FileOpen(const std::string& strFilename, EFileMode eFileMode)
		{
			size_t ioRequest = 0;
			for (ioRequest = 0; ioRequest < cMaxSimultaneousIORequests && ioRequests[ioRequest].hFile != INVALID_HANDLE_VALUE; ++ioRequest);

			if (ioRequest >= cMaxSimultaneousIORequests)
			{
				// doom?
				CR_ERROR_STRING1("Max number of open files reached when opening: %s", strFilename.c_str());
			}

			IORequest* pRequest = &ioRequests[ioRequest];

			DWORD dwDesiredAccess = 0;
			switch (eFileMode)
			{
			case EFileMode_Read:		dwDesiredAccess = GENERIC_READ; break;
			case EFileMode_Write:		dwDesiredAccess = GENERIC_WRITE; break;
			case EFileMode_ReadWrite:	dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; break;
			}

			pRequest->hFile = ::CreateFileA(strFilename.c_str(), dwDesiredAccess, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
			if (pRequest->hFile != INVALID_HANDLE_VALUE)
			{
				pRequest->overlapped.Internal = 0;	
			}
			else
			{
				ioRequest = cInvalidFileHandle;
			}
			
			return ioRequest;
		}

		void FileClose(FileHandle hFile)
		{
			IORequest* pRequest = &ioRequests[hFile];
			::CloseHandle(pRequest->hFile);
			pRequest->hFile = INVALID_HANDLE_VALUE;
		}

		void FileRead(FileHandle hFile, void* pDstBuffer, size_t bytesToRead)
		{
			IORequest* pRequest = &ioRequests[hFile];
			::ReadFile(pRequest->hFile, pDstBuffer, bytesToRead, NULL, &pRequest->overlapped);
		}

		void FileWrite(FileHandle hFile, const void* pSrcBuffer, size_t bytesToWrite)
		{
			IORequest* pRequest = &ioRequests[hFile];
			::WriteFile(pRequest->hFile, pSrcBuffer, bytesToWrite, NULL, &pRequest->overlapped);
		}

		size_t FileGetSize(const std::string& strFilename)
		{
			HANDLE hFile = ::CreateFileA(strFilename.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
			LARGE_INTEGER size;
			
			assert(hFile != INVALID_HANDLE_VALUE);
			if (hFile != INVALID_HANDLE_VALUE)
			{
				::GetFileSizeEx(hFile, &size);
				::CloseHandle(hFile);
				assert(size.HighPart == 0);
				return size.LowPart;
			}

			return 0;
		}

		size_t FileGetSize(FileHandle hFile)
		{
			LARGE_INTEGER size;
			IORequest* pRequest = &ioRequests[hFile];
			assert(pRequest->hFile != INVALID_HANDLE_VALUE);
			::GetFileSizeEx(pRequest->hFile, &size);
			assert(size.HighPart == 0);
			return size.LowPart;
		}

		bool FileBusy(FileHandle hFile)
		{
			IORequest* pRequest = &ioRequests[hFile];
			return pRequest->overlapped.Internal == STATUS_PENDING;
		}

		cr::uint64_t FileGetTimeStamp(const std::string& strFilename)
		{
			cr::uint64_t res = 0;
			FileHandle hFile = FileOpen(strFilename, EFileMode_Read);
			if(hFile != cInvalidFileHandle)
			{
				res = FileGetTimeStamp(hFile);
				FileClose(hFile);
			}
			return res;
		}
		
		cr::uint64_t FileGetTimeStamp(FileHandle hFile)
		{
			IORequest* pRequest = &ioRequests[hFile];
			ULARGE_INTEGER liRet;
			FILETIME ftWriteTime;
			GetFileTime(pRequest->hFile, NULL, NULL, &ftWriteTime);
			memcpy(&liRet, &ftWriteTime, sizeof(FILETIME));
			return liRet.QuadPart;
		}
	}
}

#endif // PF_PLATFORM_PC